<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />















  
  
  <link href="/blog/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/blog/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/blog/css/main.css?v=5.1.2" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="Hexo, NexT" />








  <link rel="shortcut icon" type="image/x-icon" href="/blog/favicon.ico?v=5.1.2" />






<meta property="og:type" content="website">
<meta property="og:title" content="汪洋">
<meta property="og:url" content="https://wang_and_only.gitee.io/blog/index.html">
<meta property="og:site_name" content="汪洋">
<meta property="og:locale">
<meta property="article:author" content="汪洋">
<meta name="twitter:card" content="summary">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '',
    scheme: 'Muse',
    sidebar: {"position":"left","display":"post","offset":12,"offset_float":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: true,
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://wang_and_only.gitee.io/blog/"/>





  <title>汪洋</title>
  














<meta name="generator" content="Hexo 5.4.0"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left 
   page-home 
 ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/blog/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">汪洋</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/blog/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/blog/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/blog/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://wang_and_only.gitee.io/blog/blog/2021/03/23/java%E5%87%BD%E6%95%B0%E5%BC%8F/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="汪洋">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2021/03/23/java%E5%87%BD%E6%95%B0%E5%BC%8F/" itemprop="url">java函数式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-23T20:30:05+08:00">
                2021-03-23
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>与近年来大火的机器学习类似，函数式编程同样是上世纪五六十年代的产物，之所以老树新花、重焕荣光，一定有其绝佳的使用场景和独特的思想内涵，让我们来一探究竟。</p>
<h2 id="什么是函数式编程"><a href="#什么是函数式编程" class="headerlink" title="什么是函数式编程"></a>什么是函数式编程</h2><h3 id="函数的定义"><a href="#函数的定义" class="headerlink" title="函数的定义"></a>函数的定义</h3><p>要解释函数式编程，就必须先定义函数。</p>
<img src="http://wang_and_only.gitee.io/just_another_repository/pics/method.png" style="zoom: 67%;" />

<p>java中的方法就是一种函数，有着参数(输入)和返回值(输出)，还可能更改你的文件(本地或远程的)，对于GUI程序，还可能改变屏幕上的像素点(也就是绘图)，当然，函数不一定会运行到return语句，中途异常退出也是有可能的。</p>
<img src="http://wang_and_only.gitee.io/just_another_repository/pics/method-def.png" style="zoom: 55%;" />

<p>我们将特定<code>环境</code>下，消耗一定时间，将<code>输入</code>转化为<code>输出</code>，且会产生一定<code>副作用</code>的实体称为函数。</p>
<p>环境即是函数执行所仰赖的基础，还是以一个java函数举例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Foo</span> <span class="keyword">implements</span> <span class="title">ApplicationContextAware</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> ApplicationContext ctx;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> member = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">setApplicationContext</span><span class="params">(ApplicationContext ctx)</span> <span class="keyword">throws</span> BeansException </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.ctx = ctx;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">bar</span><span class="params">(String arg)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">&quot;参数为:&quot;</span> + arg);</span><br><span class="line">        member += arg.size();</span><br><span class="line">        <span class="keyword">int</span> localVar = <span class="number">10</span>;</span><br><span class="line">        lovalVar = <span class="number">20</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Foo</code>类中定义了一个<code>bar</code>函数，从java程序的角度，<code>Foo</code>类就是它的环境，类对象<code>ctx</code>和<code>member</code>就成了<code>环境变量</code>，而<code>arg</code>和<code>localVar</code>不属于环境，因为它们分别是<code>输入</code>和<code>本地变量</code>，是函数的自己人，这个函数的<code>输出</code>比较特殊，是<code>void</code>。从计算机的角度，函数运行用到了CPU、硬盘、外设、显示器、网络，甚至网络连接到的网站和集群，都属于环境的一部分。</p>
<p>理论中的函数可以不考虑耗时，比如java中的<code>Function.identity()</code>，将参数原样返回，不需要计算，而现实中，这个函数的执行肯定有耗时，即使不一定达到1ms。也存在永远不会停止的函数，虽然在现实中他们总会停。</p>
<p>对<code>环境</code>产生影响的行为称为<code>副作用</code>，比如<code>member += arg.size()</code>修改了类变量，这个改动会被类的其他成员函数观察到;<code>log.info(&quot;参数为:&quot; + arg)</code>可能会打印到屏幕上，也可能修改本地文件，或者写入到远程的elastic search服务器上;容易被忽略的一点是<code>ctx</code>变量，函数可以通过它来访问和修改spring容器，这也是我们最常接触的环境之一。</p>
<h3 id="函数式编程"><a href="#函数式编程" class="headerlink" title="函数式编程"></a>函数式编程</h3><p>我们通常把具有如下特点的编程范式称为函数式编程。</p>
<ol>
<li><p>函数是一等公民<br>函数可以作为输入、输出、变量在程序中存在，将函数作为参数的函数通常被称为<code>高阶函数</code>。</p>
</li>
<li><p>变量只能赋值一次<br>在完成初始化后，变量的值就不会改变，我们称之为<code>常量</code>也不为过。</p>
</li>
<li><p>表达式代替语句</p>
<p>语句是指命令式编程中随处可见的没有返回值的操作，如if/else、switch、while块，函数式编程中用带有返回值的表达式来替代，毫无疑问，表达式也是函数的一种。</p>
</li>
<li><p>引用透明性</p>
<p>对于同样的输入，一个函数只会产生同一个输出，不受外部环境变化的影响，也就是说函数输入到输出的映射表现出确定性。</p>
</li>
<li><p>递归代替循环<br>循环的本质是通过改变指针变量（索引、迭代器等）的指向，遍历一个容器并执行操作的过程，函数式编程不支持变量的再赋值，所以只能用递归来完成遍历，函数式的数据结构也必须是递归的，如链表、树。</p>
</li>
</ol>
<p>广义上来讲，能将函数作为函数的输入和输出的编程语言都支持函数式编程，如果再加上一条，<code>函数不产生副作用</code>，就变成了<code>纯函数语言</code>，例如<code>haskell</code>。</p>
<h3 id="java的函数式支持"><a href="#java的函数式支持" class="headerlink" title="java的函数式支持"></a>java的函数式支持</h3><p>java从一开始就是命令式语言，在java7及之前的函数式支持很差，我们可以将接口作为参数或返回值来模拟函数式编程，但是非常笨拙、极难应用。java8之后，增加了lambda、Stream、Optional、Function等类函数式特性，较之前版本是质的飞跃，能极大优化业务代码，但是距离完整的函数式支持还差很远，对于函数式语言的要素如元组、柯里化、模式匹配都不支持，想体验较为纯粹的函数式编程，大家可以试试java的近亲<code>scala</code>。</p>
<h2 id="函数式编程是如何优化java代码的"><a href="#函数式编程是如何优化java代码的" class="headerlink" title="函数式编程是如何优化java代码的"></a>函数式编程是如何优化java代码的</h2><p>以目前java支持的函数式特性，我们也能够很好地优化代码，主要体现在下面几个方面。</p>
<h3 id="减少重复代码"><a href="#减少重复代码" class="headerlink" title="减少重复代码"></a>减少重复代码</h3><p>函数既是对某种转换操作的抽象，有了抽象就可以复用。</p>
<p>将对象间的比较抽象成<code>Comparator</code>，就能复用max/min(求最大最小值)，sorted(排序)的函数。</p>
<p>将集合对象处理的过程抽象成流的操作，就能复用map(类型转换)、filter(元素过滤)、distinct(去重)、reduce(集合转换为单个元素)等函数。</p>
<p>充分使用流式操作后，我们只需提供少量的特有逻辑，即可实现常用的集合转换。</p>
<h3 id="减少共享状态，增加可读性"><a href="#减少共享状态，增加可读性" class="headerlink" title="减少共享状态，增加可读性"></a>减少共享状态，增加可读性</h3><p>函数式编程要求变量只能被赋值一次，为什么要这样做呢？变量即状态，状态在多个对象间传递就成了共享状态，而共享状态往往是逻辑错误的重要源头，特别是在多线程场景下，仅赋值一次的变量显然不会有这个问题。</p>
<p>本地变量也要这样处理吗？它并不会被其他线程访问到，但是每一次的重新赋值意味着变量含义的变更。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">List&lt;User&gt; userList = getAllUser();</span><br><span class="line"><span class="comment">// part1 ...</span></span><br><span class="line">userList = userList.stream().filter(User::isOnline).collect(Collectors.toList());</span><br><span class="line"><span class="comment">// part2 ...</span></span><br><span class="line">userList = getMaleUser();</span><br><span class="line"><span class="comment">// part3 ...</span></span><br></pre></td></tr></table></figure>

<p>上面的代码具有误导性，userList在part1、part2、part3中的含义均不相同，不仔细观察很容易用错。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">List&lt;User&gt; allUserList = getAllUser();</span><br><span class="line"><span class="comment">// part1 ...</span></span><br><span class="line">List&lt;User&gt; onlineUserList = userList.stream().filter(User::isOnline).collect(Collectors.toList());</span><br><span class="line"><span class="comment">// part2 ...</span></span><br><span class="line">List&lt;User&gt; maleUserList = getMaleUser();</span><br><span class="line"><span class="comment">// part3 ...</span></span><br></pre></td></tr></table></figure>

<p>应用函数式的规则，每个变量代表了一种固定的状态，变量名充当了注释的作用，真正做到了代码自注释。</p>
<h3 id="分离副作用，更好地支持并发"><a href="#分离副作用，更好地支持并发" class="headerlink" title="分离副作用，更好地支持并发"></a>分离副作用，更好地支持并发</h3><p>副作用并不是问题本身，相反我们需要依赖它来完成业务动作，比如打印日志、读写数据库。但它会产生不确定性，我们期望函数从return处或代码末尾退出，但是副作用给函数开了另一个出口-抛异常，比如写入文件失败、网络中断，加深了复杂度。</p>
<p>不带副作用的函数（后续称为纯函数）天然支持并发，所以我们只需要让带副作用的部分支持并发，我们的代码整体就是线程安全的，这可以极大简化并发系统的设计。</p>
<h2 id="java函数式实践"><a href="#java函数式实践" class="headerlink" title="java函数式实践"></a>java函数式实践</h2><h3 id="干掉循环语句"><a href="#干掉循环语句" class="headerlink" title="干掉循环语句"></a>干掉循环语句</h3><p>为什么要干掉循环语句？难道我们的代码中能够不使用for、while吗？</p>
<p>循环语句的<code>原罪</code>在于</p>
<ol>
<li><p>迭代逻辑与处理逻辑耦合<br>试看下例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 循环初始位置</span></span><br><span class="line"><span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">while</span> (i &lt; <span class="number">10</span>) &#123; <span class="comment">// 循环终止条件</span></span><br><span class="line">    System.out.println(i); <span class="comment">// 处理逻辑</span></span><br><span class="line">    i++; <span class="comment">// 循环迭代方式</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等价于上述逻辑</span></span><br><span class="line">IntStream.iterate(<span class="number">0</span>, i -&gt; i + <span class="number">1</span>).peek(System.out::println).filter(i -&gt; i &gt;= <span class="number">9</span>).findFirst();</span><br></pre></td></tr></table></figure>

<p>可以看到，while语句的迭代逻辑和处理逻辑散落在语句中甚至语句外(int i = 0)，而函数式代码中，循环的初始化和迭代逻辑交给iterate函数，终止条件由filter和findFirst共同组成，处理逻辑在peek函数(操作流中的元素，可能会修改)中，分工明确。</p>
<p>需要注意的是，iterate或generate函数都会产生无限流，必须带上截止条件来中断执行，上例中就是以<code>找到第一个不小于9的数字</code>为截止条件的。</p>
</li>
<li><p>不可复用<br>函数是可复用的，而循环语句只能复制粘贴，必然会导致代码的可维护性变差。有人会说，C语言中可以通过宏替换来复用循环语句，这么说有一定道理，但是宏的问题在于没有类型检查，只是单纯的文本替换，所以在java中没有沿用这一特性。</p>
</li>
</ol>
<p>java标准库中的容器都支持函数式遍历操作，即<code>collection.stream().forEach()</code>。</p>
<p>而基于索引的遍历方式，可以用IntStream来替代</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数组遍历</span></span><br><span class="line">List&lt;Integer&gt; lst = ...; <span class="comment">// 初始化</span></span><br><span class="line">IntStream.range(<span class="number">0</span>, lst.size()).forEach(i -&gt; System.out.println(lst.get(i)));</span><br></pre></td></tr></table></figure>

<p>如果需要在遍历中删除元素，可以考虑使用filter，有更复杂的需求，可以参考下一节。</p>
<h3 id="实现复杂的集合操作"><a href="#实现复杂的集合操作" class="headerlink" title="实现复杂的集合操作"></a>实现复杂的集合操作</h3><p>我们接到一个奇怪的需求，要求处理一个用户列表，首先去除名称为空的用户，然后取出年龄最大的三名男性用户，求出他们的年龄之和，还要获取女性用户的全部名称，用逗号隔开，不能有重复。让我们用函数式实现一下。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Pair&lt;Integer, String&gt; <span class="title">function</span><span class="params">(List&lt;User&gt; userList)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 过滤姓名为空的用户，并按性别划分列表</span></span><br><span class="line">    userList.stream().filter(user -&gt; Map&lt;Gender, List&lt;User&gt;&gt; genderToUserList = StringUtils.isNotBlank(user.getName())).collect(Collectors.groupingBy(User::getGender));</span><br><span class="line">    <span class="comment">// 取出男性用户列表，从高到低排序取出前三，将年龄累加</span></span><br><span class="line">    Int maleAgeSum = genderToUserList.get(Gender.MALE).stream().sorted(Comparator.comparingInt(User::getAge).reversed()).limit(<span class="number">3</span>).map(User::getAge).reduce(<span class="number">0</span>, (accumulator, item) -&gt; accumulator + item);</span><br><span class="line">    <span class="comment">// 取出女性用户列表，获取名称并去重，用逗号连接</span></span><br><span class="line">    String femaleNames = genderToUserList.get(Gender.FEMALE).stream().map(User::getName).collect(Collectors.joining(<span class="string">&quot;,&quot;</span>));</span><br><span class="line">    reutrn Pair.of(maleAgeSum, femaleNames);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看起来相当麻烦的操作，采用函数式可以很轻松的完成，而且代码可读性极高。整个函数都是无副作用的，完全支持并发，写单测也很简单。</p>
<h3 id="通过函数式优化单测"><a href="#通过函数式优化单测" class="headerlink" title="通过函数式优化单测"></a>通过函数式优化单测</h3><p>函数即是抽象，抽象分离了共性与特性，让我们能更好地聚焦。以单测举例，一次单测的流程如下</p>
<img src="http://wang_and_only.gitee.io/just_another_repository/pics/unit-test-process.png" style="zoom: 100%;" />

<p>我们针对以上流程抽象出如下函数，mock使用mockito，验证使用assertJ</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 可以容纳检查异常的Consumer</span></span><br><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CheckedConsumer</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">accept</span><span class="params">(T t)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 待测试的类和函数</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestedService</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">testedMethod</span><span class="params">(Integer arg)</span> </span>&#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 待测试方法所在的类</span></span><br><span class="line"><span class="meta">@InjectMocks</span></span><br><span class="line"><span class="keyword">private</span> TestedService testedService;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法依赖http调用，通过mock对象模拟</span></span><br><span class="line"><span class="meta">@Mock</span></span><br><span class="line"><span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法依赖dubbo调用</span></span><br><span class="line"><span class="meta">@Mock</span></span><br><span class="line"><span class="keyword">private</span> DubboFacade dubboFacade;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unitTest</span><span class="params">(CheckedConsumer&lt;RestTemplate&gt; restMocker, CheckedConsumer&lt;DubboFacade&gt; dubboMocker, CheckedConsumer&lt;Void&gt; checker)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 实现mock逻辑</span></span><br><span class="line">    restMocker.accept(restTemplate);</span><br><span class="line">    dubboMocker.accept(dubboFacade);</span><br><span class="line">    <span class="comment">// 验证结果</span></span><br><span class="line">    checker.accept(<span class="keyword">null</span>);</span><br><span class="line">    <span class="comment">// 恢复mock对象</span></span><br><span class="line">    Mockito.reset(restTemplate, dubboFacade);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// mock http请求</span></span><br><span class="line">    CheckedConsumer&lt;RestTemplate&gt; restMocker = restTemplateObj -&gt; &#123;</span><br><span class="line">     	when(restTemplateObj.getForObject(anyString(), any(), anyMap())).thenReturn(<span class="string">&quot;ok&quot;</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="comment">// mock dubbo调用</span></span><br><span class="line">     CheckedConsumer&lt;DubboFacade&gt; dubboMocker = dubboFacadeObj -&gt; &#123;</span><br><span class="line">         when(dubboFacadeObj.invoke(any())).thenReturn(<span class="string">&quot;ok&quot;</span>);</span><br><span class="line">     &#125;</span><br><span class="line">    <span class="comment">// 验证输入为1时，输出为&quot;one&quot;</span></span><br><span class="line">    CheckedConsumer&lt;Void&gt; checker = arg -&gt; &#123;</span><br><span class="line">        Assertions.assertThat(testedService.testedMethod(<span class="number">1</span>)).isEqualTo(<span class="string">&quot;one&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    unitTest(restMocker, dubboMocker, checker);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它清晰地为我们自定义的部分，即mock以及验证逻辑留好了坑位，让我们更好地聚焦，而其他共性的部分留在了方法内。这种方式大家应该早有耳闻，在设计模式中称为模板方法，但是用函数式实现起来更自然。</p>
<h3 id="小议副作用"><a href="#小议副作用" class="headerlink" title="小议副作用"></a>小议副作用</h3><p>在文章的开头我们就提交了副作用，写日志、发起网络请求都算其中一种。我们的目的不是消除副作用，相反大多数程序就是为了产生副作用而运行的，最典型的例子就是读写数据库。我们真正的目的是分离出带副作用的代码，并重点关注和处理。以下面的代码为例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 关注房源</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">followHouse</span><span class="params">(Long userId, Long houseId)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 查询用户，检查用户是否有效，无效时抛异常</span></span><br><span class="line">    checkUser(userId);</span><br><span class="line">    <span class="comment">// 查询房源，检查房源是否有效，无效时抛异常</span></span><br><span class="line">    checkHouse(houseId);</span><br><span class="line">    <span class="comment">// 查询库中是否已有关注记录</span></span><br><span class="line">    <span class="keyword">boolean</span> followed = hasFollowed(userId, houseId);</span><br><span class="line">    <span class="keyword">if</span> (followed) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;用户&#123;&#125;已关注房源&#123;&#125;，不可重复关注&quot;</span>, userId, houseId);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 入库</span></span><br><span class="line">        saveFollowRecord(userId, houseId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>严格意义上来讲，这个方法中的每个子方法都有副作用，包括读写数据库、写日志，带来的问题是，每个子方法都有可能因不可控的环境因素抛出异常，如网络波动，程序可能在每一行退出，给我们排查问题增加了难度。</p>
<p>问题出在哪？我们忽略异常流程的处理，它们是看不见的<code>return</code>，而异常流程往往跟副作用绑定在一起，所以我们需要分离出带副作用的代码，单独处理。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 关注房源</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">followHouse</span><span class="params">(Long userId, Long houseId)</span> </span>&#123;</span><br><span class="line">    User user = <span class="keyword">null</span>;</span><br><span class="line">    House house = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        user = getUser(userId);</span><br><span class="line">        house = getHouse(houseId);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取用户和房源失败&quot;</span>, e);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!isUserValid(user) || !isHouseValid(house)) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;用户或房源无效&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    FollowRecord followRecord = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        followRecord = getFollowRecord(userId, houseId);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;获取关注记录失败&quot;</span>, e);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (Objects.nonNull(followRecord)) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;用户&#123;&#125;已关注房源&#123;&#125;，不可重复关注&quot;</span>, userId, houseId);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            saveFollowRecord(userId, houseId);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;保存关注记录失败&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看起来长了不少，相较于上一段代码，我们处理了可能出现异常的部分，这样函数只会在<code>return</code>或者运行完毕时退出，一个<code>确定</code>的函数才是能放心复用的。更关键的是，我们很好的分离了取数据和校验的逻辑，方便后期拓展，比如我们更换了数据源，又或者改变了校验规则。</p>
<p>那么，对于会产生副作用的代码，我们需要重点关注哪些方面呢？</p>
<ol>
<li><p>是否会抛异常，如果是怎样处理异常<br>这是我们需要首要关注的问题，未处理的异常就像地雷随时会被引爆，不能不重视。但是我们也无需陷入<code>函数式原教旨主义</code>，即妄图分离出所有的副作用，这既是不可能的(至少在java中)，也是无必要的，比如我们没有给<code>log.error</code>做异常处理，因为我们认为记录日志的操作失败的概率极低，即使失败也影响不大，所以不做处理。</p>
</li>
<li><p>是否支持并发执行</p>
<p>带副作用的部分需要并发控制，最简单的方式就是加锁，但是常用的中间件一般都支持并发，使用时需要参考相关文档。</p>
</li>
</ol>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>终点站到了！先别急着下车，如果您有java函数式方面的优秀实践，欢迎多多交流，希望能抛砖引玉，如果觉得不错，别忘👍哦！</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://wang_and_only.gitee.io/blog/blog/2021/03/05/%E6%9A%B4%E6%89%93%E7%BA%A2%E9%BB%91%E6%A0%91/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="汪洋">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2021/03/05/%E6%9A%B4%E6%89%93%E7%BA%A2%E9%BB%91%E6%A0%91/" itemprop="url">暴打红黑树</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-05T17:59:12+08:00">
                2021-03-05
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://wang_and_only.gitee.io/blog/blog/2021/03/03/hello-world/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="汪洋">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2021/03/03/hello-world/" itemprop="url">Hello World</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-03T13:41:19+08:00">
                2021-03-03
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>Welcome to <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a target="_blank" rel="noopener" href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a target="_blank" rel="noopener" href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a target="_blank" rel="noopener" href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">&quot;My New Post&quot;</span></span><br></pre></td></tr></table></figure>

<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>

<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>

<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>

<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
  </section>

  


          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      

      <section class="site-overview sidebar-panel sidebar-panel-active">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/blog/images/avatar.gif"
               alt="" />
          <p class="site-author-name" itemprop="name"></p>
           
              <p class="site-description motion-element" itemprop="description"></p>
          
        </div>
        <nav class="site-state motion-element">

          
            <div class="site-state-item site-state-posts">
              <a href="/blog/archives/">
                <span class="site-state-item-count">3</span>
                <span class="site-state-item-name">日志</span>
              </a>
            </div>
          

          

          

        </nav>

        

        <div class="links-of-author motion-element">
          
        </div>

        
        

        
        

        


      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">汪洋</span>
</div>


<div class="powered-by">
  由 <a class="theme-link" target="_blank" rel="noopener" href="https://hexo.io">Hexo</a> 强力驱动
</div>

<div class="theme-info">
  主题 -
  <a class="theme-link" target="_blank" rel="noopener" href="https://github.com/iissnan/hexo-theme-next">
    NexT.Muse
  </a>
</div>


        

        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  <script type="text/javascript" src="/blog/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/blog/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/blog/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/blog/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/blog/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/blog/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/blog/js/src/utils.js?v=5.1.2"></script>

  <script type="text/javascript" src="/blog/js/src/motion.js?v=5.1.2"></script>



  
  

  
    <script type="text/javascript" src="/blog/js/src/scrollspy.js?v=5.1.2"></script>
<script type="text/javascript" src="/blog/js/src/post-details.js?v=5.1.2"></script>

  

  


  <script type="text/javascript" src="/blog/js/src/bootstrap.js?v=5.1.2"></script>



  


  




	





  





  






  





  

  

  

  

  

  

</body>
</html>
